<?php


namespace App\Traits;

use App\Exceptions\ApiException;
use App\Helpers\LogHelper;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\ResponseInterface;
use Throwable;


/**
 * Trait GuzzleHttpTrait
 *
 * @tips    在使用的类内 use GuzzleHttpTrait
 *
 * @package App\Traits
 */
trait GuzzleHttpTrait
{
    /**
     * 链接对象
     *
     * @var Client
     */
    private Client $client;
    /**
     * HTTP 链接超时
     *
     * @var int
     */
    private $timeout = 5;

    /**
     * 重新初始化HTTP链接
     *
     * @var bool
     */
    private bool $reload = false;

    /**
     * 设置超时时间
     *
     * @param int $timeout
     */
    public function setTimeout(int $timeout)
    {
        $this->timeout = $timeout;
    }

    /**
     * 设置是否重连超时时间，每次加载后，需要重新设置
     *
     * @param bool $reload
     */
    public function setReload(bool $reload)
    {
        $this->reload = $reload;
    }

    /**
     * Guzzle Http Client
     *
     * @return Client
     */
    private function getClient(): Client
    {
        if ($this->reload || !isset($this->client)) {
            $this->client = new Client();

            $this->reload = false;
        }

        return $this->client;
    }

    /**
     * 处理Response返回的数据
     *
     * @param ResponseInterface $response
     * @param bool $origin
     * @return mixed|string
     * @throws ApiException
     */
    private function getResponseData(ResponseInterface $response, bool $origin = false)
    {
        $statusCode = $response->getStatusCode();
        if ($statusCode != 200) {
            $msgArr = [
                'statusCode' => $statusCode,
                'reason' => $response->getReasonPhrase(),
            ];
            throw new ApiException(json_encode($msgArr), ApiException::CODE_2029);

        }
        $body = $response->getBody()->getContents();

        return $origin ? $body : json_decode($body, true);
    }

    /**
     * get请求
     *
     * @param string $url 请求地址
     * @param array $query query参数
     * @param bool $origin 返回数据是否需要json_decode
     * @return mixed|array
     * @throws ApiException
     */
    public function guzzleHttpGet(string $url, array $query = [], bool $origin = false)
    {
        $this->getClient();
        $options = [];
        if ($query) {
            $options['query'] = $query;
        }

        return $this->httpClient('GET', $url, $options, $origin);
    }


    /**
     * post请求
     *
     * @param string $url 请求地址
     * @param array $params 请求参数
     * @param bool $origin 返回数据是否需要json_decode
     * @return mixed|array
     * @throws ApiException
     */
    public function guzzleHttpPost(string $url, array $params = [], bool $origin = false)
    {
        self::getClient();

        if (empty($params['headers'])) {
            $params['headers'] = [
                'Content-type' => 'application/json',
            ];
        }
        
        return $this->httpClient('POST', $url, $params, $origin);
    }

    /**
     * 请求
     *
     * @param string $verb 请求方法
     * @param string $url 请求地址
     * @param array $options 请求参数
     * @param false $origin 返回数据是否需要json_decode
     * @return mixed|string|void
     * @throws ApiException
     */
    private function httpClient(string $verb, string $url, array $options = [], bool $origin = false)
    {
        if (empty($options['timeout'])) {
            $options['timeout'] = $this->timeout;
        }

        try {
            // 请求获取响应
            $optionsForLog = $options;
            if (isset($optionsForLog["json"]["works_data"])) {
                $optionsForLog["json"] = array_merge($optionsForLog["json"], ['works_data' => ""]);
            }
            LogHelper::info('guzzle', ['start', $verb, $url, $optionsForLog, $origin]);

            $response = $this->client->request($verb, $url, $options);
            // 加工数据
            $res = $this->getResponseData($response, $origin);

            is_array($res) && LogHelper::info('guzzle', ['end', $res]);
            return $res;
        } catch (RequestException|GuzzleException $ex) {
            $this->logGuzzleHttpException($ex); // @tips 可自定义处理
        } catch (Throwable $ex) {
            $this->logGuzzleHttpException($ex);
        }
    }

    /**
     * 记录错误日志
     *
     * @param RequestException $ex
     * @throws ApiException
     */
    protected function logGuzzleHttpException(Throwable $ex)
    {
        $errContext = $ex->getHandlerContext();
        $errno = $errContext['errno'] ?? 0;
        $error = $errno == 28 ? '链接第三方系统超时，请稍候重试。' : 'https请求异常：errcode = ' . $ex->getCode() . ', errmsg = "' . $ex->getMessage() . '"';

        LogHelper::error('guzzle', $error);

        throw new ApiException($error, ApiException::CODE_2028);
    }
}
